package com.lzh.swagger.config;

import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import com.lzh.swagger.builder.LzhRequestHandlerSelectors;
import com.lzh.swagger.properties.SwaggerProperties;
import com.lzh.swagger.properties.SwaggerProperties.DocketInfo;
import com.lzh.swagger.properties.SwaggerProperties.GlobalOperationParameter;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.*;

/**
 * @author zhehen.lu
 * @date 2024/7/3 14:46
 */
@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
@EnableConfigurationProperties({SwaggerProperties.class})
public class LzhSwaggerConfig implements BeanFactoryAware, InitializingBean {
    private static final String AUTH_KEY = "Authorization";
    private BeanFactory beanFactory;
    @Autowired
    private SwaggerProperties swaggerProperties;

    public LzhSwaggerConfig() {
        // TODO document why this constructor is empty
    }

    @Bean
    @ConditionalOnMissingBean
    public SwaggerProperties swaggerProperties() {
        return new SwaggerProperties();
    }

    public void creatDockets() {
        if (this.swaggerProperties.getEnabled()) {
            if (this.swaggerProperties.getDocket().isEmpty()) {
                this.createDocket(new DocketInfo(), this.swaggerProperties.getHost(), "default");
            } else {
                this.swaggerProperties.getDocket().forEach((key, value) -> this.createDocket(value, this.swaggerProperties.getHost(), key));
            }
        }
    }

    private Docket createDocket(DocketInfo docketInfo, String host, String groupName) {
        ApiInfo apiInfo = this.getApiInfo(docketInfo);
        if (docketInfo.getBasePath().isEmpty()) {
            docketInfo.getBasePath().add("/**");
        }

        List<Predicate<String>> basePath = new ArrayList<>();

        for (String path : docketInfo.getBasePath()) {
            basePath.add(PathSelectors.ant(path));
        }

        List<Predicate<String>> excludePath = new ArrayList<>();

        for (String path : docketInfo.getExcludePath()) {
            excludePath.add(PathSelectors.ant(path));
        }

        Docket docket = (new Docket(DocumentationType.SWAGGER_2))
                .host(host)
                .groupName(groupName)
                .apiInfo(apiInfo)
                .useDefaultResponseMessages(false)
                .produces(new HashSet<>(Collections.singletonList("application/json")))
                .securitySchemes(this.securitySchemes())
                .globalOperationParameters(this.buildGlobalOperationParametersFromSwaggerProperties(docketInfo.getGlobalOperationParameters()))
                .select()
                .apis(StringUtils.isEmpty(docketInfo.getBasePackage()) ? RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class) : LzhRequestHandlerSelectors.basePackage(docketInfo.getBasePackage()))
                .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath)))
                .build();
        ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory)this.beanFactory;
        configurableBeanFactory.registerSingleton(groupName, docket);
        return docket;
    }

    private ApiInfo getApiInfo(DocketInfo docketInfo) {
        return (new ApiInfoBuilder())
                .title(docketInfo.getTitle())
                .description(docketInfo.getDesc())
                .version(docketInfo.getVersion())
                .license(docketInfo.getLicense())
                .licenseUrl(docketInfo.getLicenseUrl())
                .contact(new Contact(docketInfo.getContact().getName(), docketInfo.getContact().getUrl(), docketInfo.getContact().getEmail())).termsOfServiceUrl(docketInfo.getTermsOfServiceUrl())
                .build();
    }

    private List<Parameter> buildGlobalOperationParametersFromSwaggerProperties(List<GlobalOperationParameter> globalOperationParameters) {
        List<Parameter> parameters = Lists.newArrayList();
        if (Objects.nonNull(globalOperationParameters)) {
            for (GlobalOperationParameter globalOperationParameter : globalOperationParameters) {
                parameters.add((new ParameterBuilder())
                        .name(globalOperationParameter.getName())
                        .description(globalOperationParameter.getDesc())
                        .modelRef(new ModelRef(globalOperationParameter.getModelRef())).parameterType(globalOperationParameter.getParameterType()).required(globalOperationParameter.getRequire())
                        .build());
            }
        }
        return parameters;
    }

    private List<ApiKey> securitySchemes() {
        List<ApiKey> apiKeys = new ArrayList<>(1);
        ApiKey apiKey = new ApiKey(AUTH_KEY, AUTH_KEY, "header");
        apiKeys.add(apiKey);
        return apiKeys;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void afterPropertiesSet() {
        this.creatDockets();
    }
}